Advanced tmap

We’ve already made many simple maps using the tmap package. Now that we know more about cartographic design, we can explore the more advanced features of tmap. This chapter does not cover everything. For much more information on making good maps in tmap, this is a great resource: Spatial Data Visualization with tmap

After completing these exercises, you should be able to:

  • Apply visual variables other than color (size, shape)
  • Select color palettes
  • Select appropriate classification schemes
  • Make interactive and static maps using a basemap
  • Add basic formatting to a tmap

We will use our NC county boundary and Durham schools data to practice our tmap visualization.

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(sf)
Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
library(tmap)

nc_counties <- st_read("https://drive.google.com/uc?export=download&id=1g9sGIikgOEubqoj97fUVoCYAKlBDVX5a")
Reading layer `nc_county_2020_pop' from data source 
  `https://drive.google.com/uc?export=download&id=1g9sGIikgOEubqoj97fUVoCYAKlBDVX5a' 
  using driver `GeoJSON'
Simple feature collection with 100 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32182 ymin: 33.75288 xmax: -75.40012 ymax: 36.58814
Geodetic CRS:  NAD83
durham_schools <- st_read("https://drive.google.com/uc?export=download&id=1ajKBLeKpFMW8z0HCbnLi4C44tgryCsRt") 
Reading layer `durham_schools' from data source 
  `https://drive.google.com/uc?export=download&id=1ajKBLeKpFMW8z0HCbnLi4C44tgryCsRt' 
  using driver `GeoJSON'
Simple feature collection with 74 features and 28 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -78.95519 ymin: 35.93524 xmax: -78.81451 ymax: 36.05743
Geodetic CRS:  WGS 84

Visual Variables

Color

We are already familiar with making a basic tmap using color to visualize patterns:

tm_shape(nc_counties) + tm_polygons(fill = "POP2020")

Size

We can also represent variables by size

tm_shape(nc_counties) + tm_symbols(size = "POP2020")

Size and Color

Or both!

tm_shape(nc_counties) + tm_symbols(size = "POP2020", fill = "POP2020")

Shape

For categorical variables, we can also use shape

#with symbols there are a default of 5 symbols. If you have more than 5 categories, you need to specify which shapes to use using the shape.scale() argument
tm_shape(durham_schools) + tm_symbols(shape = "factype", shape.scale = tm_scale(values = c(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)))

Color Palettes

We can also modify the color palette. To see all the available color palettes, you should run the command below

cols4all::c4a_gui()
tm_shape(nc_counties) + tm_polygons(fill = "POP2020", fill.scale = tm_scale_intervals(values = "bu_pu"))

Classification Schemes

One of the biggest decisions we can make is how to classify our data. You can see in the maps above that tmap defaults to an equal interval color scheme. However, this is often not the best way to classify the data. Your decision about classification should be made based on the distribution of the data. Let’s look at the distribution of the median age variable

ggplot(nc_counties, aes(x = POP2020)) + geom_histogram()

Because so much of our variation is between below 200,000, using an equal interval color palette makes it difficult to visualize the majority of the variation. See how different the map looks when we use a quantile classification scheme?

tm_shape(nc_counties) + tm_polygons(fill = "POP2020", fill.scale = tm_scale_intervals(values = "bu_pu", style = "quantile"))

The following classification schemes will cover most of your uses:

  • equal
  • pretty
  • quantile
  • fisher (natural breaks)

Interactive Mapping

So far, we have only made mostly static maps. However, tmap has the availability to make interactive maps as well.

## make an interactive map
tmap_mode(mode = "view")

tm_shape(nc_counties) + tm_polygons(fill = "POP2020", fill.scale = tm_scale_intervals(values = "bu_pu", style = "quantile"))

Formatting

Adding Transparency and additional classes

tm_shape(nc_counties) + tm_polygons(fill = "POP2020", fill_alpha = .3, fill.scale = tm_scale_intervals(values = "bu_pu", style = "quantile", n = 7))

Adding a Basemap and a Title

#change back to static plot
tmap_mode(mode = "plot")

tm_shape(nc_counties) + tm_polygons(fill = "POP2020",fill.scale = tm_scale_intervals(values = "bu_pu", style = "quantile", n = 5), fill_alpha = .3) +
tm_title("Median Age by Census Block Group") + tm_basemap("OpenStreetMap")

The following basemaps will cover most of your uses:

  • “CartoDB.VoyagerOnlyLabels” - Labels Only
  • “CartoDB.PositronNoLabels”- No Labels
  • “OpenStreetMap” - Labeled Basemap
  • “Esri.WorldImagery” - Satellite

Layout

tmap provides advanced functionality for map formatting, which is explored in greater detail here. For example, we can make substantially improve the formatting of the map above

tm_shape(nc_counties) +
  tm_polygons(
    fill = "POP2020",
    fill.scale = tm_scale_intervals(
      values = "bu_pu",
      style = "quantile",
      n = 5
    ),
    fill_alpha = 0.3,
    fill.legend = tm_legend(
      position = tm_pos_in("left", "bottom"),
      frame = TRUE,
      frame.r = 6,
      bg.color = "white",
      item.height = 0.55,
      item.width  = 0.55
    )
  ) +
  tm_title("2020 Population by NC County", size = 2) +
  tm_basemap("OpenStreetMap") +
  tm_layout(
    text.fontfamily = "serif",
    frame = TRUE,
    frame.r = 15
  )
Multiple palettes called "bu_pu" found: "brewer.bu_pu", "matplotlib.bu_pu". The first one, "brewer.bu_pu", is returned.